package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.TextureKey;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.heightmap.AbstractHeightMap;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.post.Filter;
import com.jme3.post.filters.BloomFilter;
import com.jme3.post.filters.CartoonEdgeFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.scene.shape.Dome;
import com.jme3.scene.shape.Sphere;
import java.text.MessageFormat;

/**
 * Example 12 - how to give objects physical properties so they bounce and fall.
 * @author base code by double1984, updated by zathras
 */
public class Main extends SimpleApplication implements ActionListener {
    boolean grayScaleEnabled = false;
    boolean sepiaEnabled = false;
    boolean cartoonEdgeEnabled = false;
    boolean bloomEnabled = false;
    float flyCamSpeed = 25f;
    DirectionalLight sun;
    Node sunNode = new Node();
    Geometry sunGeo;
    Vector3f lightDirection = new Vector3f(0, -1, 0);
    float sunAngle = 0f;
    float sunIntensity = 0.5f;
    float sunIntensityDelta = 0.001f;
    Filter grayscaleFilter = new GrayscaleFilter();
    Filter sepiaFilter = new SepiaFilter();
    Filter cartoonEdgeFilter = new CartoonEdgeFilter();
    Filter bloomFilter = new BloomFilter();

    public static void main(String args[]) {
        Main app = new Main();
        app.start();
    }
    /** Prepare the Physics Application State (jBullet) */
    private BulletAppState bulletAppState;
    /** Prepare Materials */
    Material wall_mat;
    VehicleControl vehicle;
    private final float accelerationForce = 1000.0f;
    private final float brakeForce = 100.0f;
    private float steeringValue = 0;
    private float accelerationValue = 0;
    private Vector3f jumpForce = new Vector3f(0, 3000, 0);
    /** Prepare geometries and physical nodes for bricks and cannon balls. */
    private RigidBodyControl brick_phy;
    private static final Box box;
    /** dimensions used for bricks and wall */
    private static final float brickLength = 0.48f;
    private static final float brickWidth = 0.24f;
    private static final float brickHeight = 0.12f;

    static {
        /** Initialize the brick geometry */
        box = new Box(Vector3f.ZERO, brickLength, brickHeight, brickWidth);
        box.scaleTextureCoordinates(new Vector2f(1f, .5f));
    }

    private void setupKeys() {
        inputManager.addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addListener(this, "shoot");

        inputManager.addMapping("accelerateCam", new KeyTrigger(KeyInput.KEY_ADD));
        inputManager.addMapping("deccelerateCam", new KeyTrigger(KeyInput.KEY_MINUS));
        inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_H));
        inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K));
        inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U));
        inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J));
        inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));

        inputManager.addMapping("toggleGrayscale", new KeyTrigger(KeyInput.KEY_1));
        inputManager.addListener(this, "toggleGrayscale");
        inputManager.addMapping("toggleSepia", new KeyTrigger(KeyInput.KEY_2));
        inputManager.addListener(this, "toggleSepia");
        inputManager.addMapping("toggleCartoonEdge", new KeyTrigger(KeyInput.KEY_3));
        inputManager.addListener(this, "toggleCartoonEdge");
        inputManager.addMapping("toggleBloom", new KeyTrigger(KeyInput.KEY_4));
        inputManager.addListener(this, "toggleBloom");
        
        
        inputManager.addListener(this, "accelerateCam");
        inputManager.addListener(this, "deccelerateCam");
        inputManager.addListener(this, "Lefts");
        inputManager.addListener(this, "Rights");
        inputManager.addListener(this, "Ups");
        inputManager.addListener(this, "Downs");
        inputManager.addListener(this, "Space");
        inputManager.addListener(this, "Reset");
    }

    @Override
    public void simpleInitApp() {
        Sphere sunSphere = new Sphere(10, 10, 10f);
        sunGeo = new Geometry("sun", sunSphere);
        
     
        
        Material sunMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        sunMaterial.getAdditionalRenderState().setWireframe(false);
        sunMaterial.setColor("Color", ColorRGBA.Yellow);
        sunGeo.setMaterial(sunMaterial);
        
        sunGeo.setLocalTranslation(new Vector3f(0f, 200f, 0f));
        sunNode.attachChild(sunGeo);
        rootNode.attachChild(sunNode);

        sun = new DirectionalLight();
        sun.setDirection(lightDirection);
        rootNode.addLight(sun);
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(new ColorRGBA(0.2f, 0.2f, 0.2f, 0.2f));
        rootNode.addLight(ambient);

        /** Set up Physics Game */
        flyCam.setMoveSpeed(25f);
        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        //bulletAppState.getPhysicsSpace().enableDebug(assetManager);
        bulletAppState.getPhysicsSpace().setAccuracy(1f / 240f);
        
  
        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        grayscaleFilter.setEnabled(grayScaleEnabled);
        sepiaFilter.setEnabled(sepiaEnabled);
        cartoonEdgeFilter.setEnabled(cartoonEdgeEnabled);
        bloomFilter.setEnabled(bloomEnabled);
        
        fpp.addFilter(grayscaleFilter);
        fpp.addFilter(sepiaFilter);
        fpp.addFilter(cartoonEdgeFilter);
        fpp.addFilter(bloomFilter);
        viewPort.addProcessor(fpp);

        /** Configure cam to look at scene */
        cam.setLocation(new Vector3f(0, 4f, 6f));
        cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);
        /** Initialize the scene, materials, and physics space */
        setupKeys();
        initMaterials();
        //initCubeWorld();
        initWall();
        initLoop();
        initFloor();
        vehicle = VehicleFactory.createVehicle(assetManager, rootNode, bulletAppState);

        Box shaderBox = new Box(5.0f, 5.0f, 5.0f);
        Geometry shaderBoxGeo = new Geometry("shaderbox", shaderBox);
        shaderBoxGeo.setLocalTranslation(0, 0, -10);
        shaderBoxGeo.setMaterial(new Material(assetManager, "/MatDefs/SolidColor.j3md"));
        rootNode.attachChild(shaderBoxGeo);


        
        Dome d = new Dome(10, 20, 3);
        Geometry dgeo = new Geometry("domegeo", d);
        dgeo.setMaterial(wall_mat);
        rootNode.attachChild(dgeo);
        

        
        rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/spheremap_irrlicht.jpg", true));
        Texture t;
        try {
            initTerrain();
            //initCrossHairs();
        } catch (Exception ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Load a model from test_data (OgreXML + material + texture)
        Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
        ninja.scale(0.05f, 0.05f, 0.05f);
        ninja.rotate(0.0f, -3.0f, 0.0f);
        ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
        ninja.move(0f, 1f, 20f);
        ninja.setMaterial(wall_mat);
        rootNode.attachChild(ninja);
        ninja.addControl(new RigidBodyControl(0f));
        bulletAppState.getPhysicsSpace().addAll(ninja);

    }

    public void initCubeWorld() {
        Node node = new Node();
        //node.setLocalTranslation(0, 50, 0);
        for (int i = 0; i < 100; i++) {
            for (int j = 0; j < 100; j++) {
                Box cube = new Box(1.0f, 1.0f, 1.0f);
                Geometry cubeGeo = new Geometry(MessageFormat.format("box {0} {1}", i, j), cube);
                cubeGeo.setMaterial(wall_mat);
                cubeGeo.setLocalTranslation(i * 2, 0, j * 2);
                node.attachChild(cubeGeo);
                //RigidBodyControl cube_phy = new RigidBodyControl(0.0f);
                //cube_phy.setCollisionShape(new BoxCollisionShape(new Vector3f(1.0f, 1.0f, 1.0f)));
                //cube_phy.setKinematic(false);
                //cubeGeo.addControl(cube_phy);
                //bulletAppState.getPhysicsSpace().add(cube_phy);
            }
        }
        rootNode.attachChild(node);
    }

    /** Make a solid floor and add it to the scene. */
    public void initFloor() {
        /** Initialize the floor geometry */
        Box floor = new Box(Vector3f.ZERO, 100f, 0.1f, 50f);
        floor.scaleTextureCoordinates(new Vector2f(3, 6));

        
        Material floor_mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        TextureKey key3 = new TextureKey("Textures/Terrain/Pond/Pond.jpg");
        key3.setGenerateMips(true);
        Texture tex3 = assetManager.loadTexture(key3);
        tex3.setWrap(WrapMode.Repeat);
        floor_mat.setTexture("m_DiffuseMap", tex3);

        
        Geometry floor_geo = new Geometry("Floor", floor);
        floor_geo.setMaterial(floor_mat);
        floor_geo.setLocalTranslation(0, -0.1f, 0);
        this.rootNode.attachChild(floor_geo);
        /* Make the floor physical with mass 0.0f! */
        RigidBodyControl floor_phy = new RigidBodyControl(0.0f);
        floor_geo.addControl(floor_phy);
        bulletAppState.getPhysicsSpace().add(floor_phy);
    }

    public void onAction(String binding, boolean value, float tpf) {
        if (binding.equals("accelerateCam")) {
            flyCamSpeed *= 2;
        }
        if (binding.equals("deccelerateCam")) {
            flyCamSpeed /= 2;
        }
        flyCam.setMoveSpeed(flyCamSpeed);
        
        if (binding.equals("toggleGrayscale") && !value) {
            grayScaleEnabled = !grayScaleEnabled;
            grayscaleFilter.setEnabled(grayScaleEnabled);
        }
        
        if (binding.equals("toggleBloom") && !value) {
            bloomEnabled = !bloomEnabled;
            bloomFilter.setEnabled(bloomEnabled);
        }
        
        if (binding.equals("toggleSepia") && !value) {
            sepiaEnabled = !sepiaEnabled;
            sepiaFilter.setEnabled(sepiaEnabled);
        }
        
        if (binding.equals("toggleCartoonEdge") && !value) {
            cartoonEdgeEnabled = !cartoonEdgeEnabled;
            cartoonEdgeFilter.setEnabled(cartoonEdgeEnabled);
        }
        
        
        if (binding.equals("shoot") && !value) {
            CannonBallFactory.getInstance(assetManager, wall_mat).makeCannonBall(rootNode, bulletAppState, cam.getLocation(), cam.getDirection());
        }

        if (binding.equals("Lefts")) {
            if (value) {
                steeringValue += .5f;
            } else {
                steeringValue += -.5f;
            }
            vehicle.steer(steeringValue);
        } else if (binding.equals("Rights")) {
            if (value) {
                steeringValue += -.5f;
            } else {
                steeringValue += .5f;
            }
            vehicle.steer(steeringValue);
        } else if (binding.equals("Ups")) {
            if (value) {
                accelerationValue += accelerationForce;
            } else {
                accelerationValue -= accelerationForce;
            }
            vehicle.accelerate(accelerationValue);
        } else if (binding.equals("Downs")) {
            if (value) {
                vehicle.brake(brakeForce);
            } else {
                vehicle.brake(0f);
            }
        } else if (binding.equals("Space")) {
            if (value) {
                grayscaleFilter.setEnabled(!grayscaleFilter.isEnabled());
                vehicle.applyImpulse(jumpForce, Vector3f.ZERO);
            }
        } else if (binding.equals("Reset")) {
            if (value) {
                System.out.println("Reset");
                vehicle.setPhysicsLocation(Vector3f.ZERO);
                vehicle.setPhysicsRotation(new Matrix3f());
                vehicle.setLinearVelocity(Vector3f.ZERO);
                vehicle.setAngularVelocity(Vector3f.ZERO);
                vehicle.resetSuspension();
            } else {
            }
        }
    }

    /** Initialize the materials used in this scene. */
    public void initMaterials() {
        wall_mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        TextureKey key = new TextureKey("Textures/Terrain/BrickWall/BrickWall.jpg");
        key.setGenerateMips(true);
        Texture tex = assetManager.loadTexture(key);
        wall_mat.setTexture("m_DiffuseMap", tex);
    }

    /** This loop builds a wall out of individual bricks. */
    public void initWall() {
        float startpt = brickLength / 4;
        float height = 0;
        for (int j = 0; j < 15; j++) {
            for (int i = 0; i < 6; i++) {
                Vector3f vt = new Vector3f(-20f + i * brickLength * 2 + startpt, brickHeight + height, 0);
                makeBrick(vt);
            }
            startpt = -startpt;
            height += 2 * brickHeight;
        }
    }

    public void initLoop() {
        int segments = 32;
        float radius = 10f;
        Node loop = new Node();
        loop.move(20f, radius, 0f);
        float width = 2.5f;

        for (int i = 0; i < segments; i++) {
            Box segment = new Box(width, FastMath.PI * radius / segments, 0.1f);
            segment.scaleTextureCoordinates(new Vector2f(1f, FastMath.PI * radius / segments));
            Geometry gSegment = new Geometry("segment " + i, segment);

            Node node = new Node();
            gSegment.move(2 * width * i / segments, 0f, radius);

            node.attachChild(gSegment);
            Quaternion q = new Quaternion(new float[]{FastMath.HALF_PI + FastMath.TWO_PI * i / segments, 0f, 0f});
            node.rotate(q);

            gSegment.setMaterial(wall_mat);
            loop.attachChild(node);

            RigidBodyControl segment_phy = new RigidBodyControl(0.0f);
            gSegment.addControl(segment_phy);
            bulletAppState.getPhysicsSpace().add(segment_phy);

        }
        rootNode.attachChild(loop);
    }

    public void initTerrain() throws Exception {
        TerrainQuad terrain;
        Material mat_terrain;

        /** 1. Create terrain material and load four textures into it. */
        mat_terrain = new Material(assetManager,
                "Common/MatDefs/Terrain/Terrain.j3md");

        /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */
        mat_terrain.setTexture("Alpha", assetManager.loadTexture(
                "Textures/Terrain/splat/alphamap.png"));

        /** 1.2) Add GRASS texture into the red layer (Tex1). */
        Texture grass = assetManager.loadTexture(
                "Textures/Terrain/splat/grass.jpg");
        grass.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex1", grass);
        mat_terrain.setFloat("Tex1Scale", 64f);

        /** 1.3) Add DIRT texture into the green layer (Tex2) */
        Texture dirt = assetManager.loadTexture(
                "Textures/Terrain/splat/dirt.jpg");
        dirt.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex2", dirt);
        mat_terrain.setFloat("Tex2Scale", 32f);

        /** 1.4) Add ROAD texture into the blue layer (Tex3) */
        Texture rock = assetManager.loadTexture(
                "Textures/Terrain/splat/road.jpg");
        rock.setWrap(WrapMode.Repeat);
        mat_terrain.setTexture("Tex3", rock);
        mat_terrain.setFloat("Tex3Scale", 128f);

        /** 2. Create the height map */
        AbstractHeightMap heightmap = null;
        Texture heightMapImage = assetManager.loadTexture(
                "Textures/Terrain/splat/mountains512.png");
        heightmap = new ImageBasedHeightMap(heightMapImage.getImage());
        heightmap.load();

        /** 3. We have prepared material and heightmap. 
         * Now we create the actual terrain:
         * 3.1) Create a TerrainQuad and name it "my terrain".
         * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65.
         * 3.3) We prepared a heightmap of size 512x512 -- so we supply 512+1=513.
         * 3.4) As LOD step scale we supply Vector3f(1,1,1).
         * 3.5) We supply the prepared heightmap itself.
         */
        int patchSize = 65;
        terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap());

        /** 4. We give the terrain its material, position & scale it, and attach it. */
        terrain.setMaterial(mat_terrain);
        terrain.setLocalTranslation(0, -100, 0);
        terrain.setLocalScale(2f, 0.5f, 2f);
        rootNode.attachChild(terrain);

        /** 5. The LOD (level of detail) depends on were the camera is: */
        TerrainLodControl control = new TerrainLodControl(terrain, getCamera());
        terrain.addControl(control);
        terrain.addControl(new RigidBodyControl(0));
        bulletAppState.getPhysicsSpace().addAll(terrain);
    }

    /** This method creates one individual physical brick. */
    public void makeBrick(Vector3f loc) {
        /** Create a brick geometry and attach to scene graph. */
        Geometry brick_geo = new Geometry("brick", box);
        brick_geo.setMaterial(wall_mat);
        rootNode.attachChild(brick_geo);
        /** Position the brick geometry  */
        brick_geo.setLocalTranslation(loc);
        /** Make brick physical with a mass > 0.0f. */
        brick_phy = new RigidBodyControl(2f);
        /** Add physical brick to physics space. */
        brick_geo.addControl(brick_phy);
        bulletAppState.getPhysicsSpace().add(brick_phy);
    }

    /** A plus sign used as crosshairs to help the player with aiming.*/
    protected void initCrossHairs() {
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize() * 2);
        ch.setText("+");        // fake crosshairs :)
        ch.setLocalTranslation( // center
                settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2,
                settings.getHeight() / 2 + ch.getLineHeight() / 2, 0);
        guiNode.attachChild(ch);
    }

    @Override
    public void simpleUpdate(float tpf) {
        sunAngle += 0.00000001f;
        sunNode.rotate(sunAngle, 0f, 0f);
        Vector3f sunPos = sunGeo.getWorldTranslation();
        Vector3f sunDir = sunPos.mult(-1);
        sunDir.normalizeLocal();
        /*Vector3f dir = q.mult(lightDirection);
        if (sunIntensity > 0.9f || sunIntensity < 0.1) {
            sunIntensityDelta = -sunIntensityDelta;
        }
        sunIntensity += sunIntensityDelta;*/
        //sunNode.setLocalRotation(new Quaternion(1f, 0f, 0f, sunAngle));
        //sun.setColor(new ColorRGBA(sunIntensity, sunIntensity, sunIntensity, 1.0f));
        sun.setDirection(sunDir);
    }

    
}